10.2 值

和Type获取类型信息不同,Value专注于对象实例数据读写。

在前面章节曾提到过,接口变量会复制对象,且是unaddressable的,所以要想修改目标对象,就必须使用指针。

func main() { a:=100 va,vp:=reflect.ValueOf(a),reflect.ValueOf(&a).Elem()

fmt.Println(va.CanAddr(),va.CanSet()) fmt.Println(vp.CanAddr(),vp.CanSet()) }

输出:

false false true true

就算传入指针,一样需要通过Elem获取目标对象。因为被接口存储的指针本身是不能寻址和进行设置操作的。

注意,不能对非导出字段直接进行设置操作,无论是当前包还是外包。

type User struct{ Name string code int }

func main() { p:=new(User) v:=reflect.ValueOf(p).Elem()

name:=v.FieldByName(“Name”) code:=v.FieldByName(“code”)

fmt.Printf(“name:canaddr= %v,canset= %v\n”,name.CanAddr(),name.CanSet()) fmt.Printf(“code:canaddr= %v,canset= %v\n”,code.CanAddr(),code.CanSet())

if name.CanSet() { name.SetString(“Tom”) }

if code.CanAddr() { *(*int)(unsafe.Pointer(code.UnsafeAddr())) =100 }

fmt.Printf(”%+v\n”, *p) }

输出:

name:canaddr=true,canset=true code:canaddr=true,canset=false

{Name:Tom code:100}

Value.Pointer和Value.Int等方法类似,将Value.data存储的数据转换为指针,目标必须是指针类型。而UnsafeAddr返回任何CanAddr Value.data地址(相当于&取地址操作),比如Elem后的Value,以及字段成员地址。

以结构体里的指针类型字段为例,Pointer返回该字段所保存的地址,而UnsafeAddr返回该字段自身的地址(结构对象地址+偏移量)。

可通过Interface方法进行类型推断和转换。

func main() { type user struct{ Name string Age int }

u:=user{ “q.yuhen”, 60, }

v:=reflect.ValueOf(&u)

if!v.CanInterface() { println(“CanInterface:fail.”) return }

p,ok:=v.Interface().(*user)

if!ok{ println(“Interface:fail.”) return }

p.Age++ fmt.Printf(”%+v\n”,u) }

输出:

{Name:q.yuhen Age:61}

也可直接使用Value.Int、Bool等方法进行类型转换,但失败时会引发panic,且不支持ok-idiom。

复合类型对象设置示例:

func main() { c:=make(chan int,4) v:=reflect.ValueOf(c)

if v.TrySend(reflect.ValueOf(100)) { fmt.Println(v.TryRecv()) } }

输出:

100 true

接口有两种nil状态,这一直是个潜在麻烦。解决方法是用IsNil判断值是否为nil。

func main() { var a interface{} =nil var b interface{} = (*int)(nil)

fmt.Println(anil) fmt.Println(bnil,reflect.ValueOf(b).IsNil()) }

输出:

true false true

也可用unsafe转换后直接判断iface.data是否为零值。

func main() { var b interface{} = (int)(nil) iface:= ([2]uintptr)(unsafe.Pointer(&b))

fmt.Println(iface,iface[1] ==0) }

输出:

&[712160 0]true

让人很无奈的是,Value里的某些方法并未实现ok-idom或返回error,所以得自行判断返回的是否为Zero Value。

func main() { v:=reflect.ValueOf(struct{name string}{})

println(v.FieldByName(“name”).IsValid()) println(v.FieldByName(“xxx”).IsValid()) }

输出:

true false